package org.snia.server.service;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.SequenceInputStream;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Vector;

import javax.naming.InitialContext;
import javax.naming.NamingException;

import org.snia.cdmi.Constants;
import org.snia.cdmi.ErrorCodes;
import org.snia.cdmi.auth.Principal;
import org.snia.cdmi.exception.ForbiddenException;
import org.snia.cdmi.exception.InternalException;
import org.snia.cdmi.exception.NotFoundException;
import org.snia.cdmi.locator.KetamaNodeLocator;
import org.snia.cdmi.locator.Node;
import org.snia.cdmi.model.AccessControlList;
import org.snia.cdmi.model.Container;
import org.snia.cdmi.model.ContainerStatus;
import org.snia.cdmi.model.DataObject;
import org.snia.cdmi.model.DataObjectStatus;
import org.snia.cdmi.model.DataObjectVersion;
import org.snia.cdmi.model.InputDataObject;
import org.snia.cdmi.model.MetaContainer;
import org.snia.cdmi.model.MetaDataObject;
import org.snia.cdmi.model.MetaSliceData;
import org.snia.server.ejb.cache.CacheOperation;
import org.snia.server.ejb.container.ContainerOperation;
import org.snia.server.ejb.container.LocalContainerOperation;
import org.snia.server.ejb.dataobject.DataObjectOperation;
import org.snia.server.ejb.dataobject.LocalDataObjectOperation;
import org.snia.server.ejb.dataslice.DataSliceOperation;
import org.snia.server.ejb.entitydata.EntityDataBean;
import org.snia.server.ejb.entitydata.EntityDataOperation;
import org.snia.util.JuuidUtils;
import org.snia.util.SerializeUtil;
import org.snia.util.ServiceUtils;

public class DataObjectService {
	
	private Principal principal;

	private static int MODE = 0;// 从配置文件中读取。
	private static int SHARE_MODE = 0;// 0：表示Share模式，1：表示Share-Nothing模式

	// @EJB(beanName="DataObjectBean") DataObjectOperation local;
	// @EJB(beanName="DataObjectBean") RemoteDataObjectOperation remote;
	private InitialContext ctx = null;
	private CacheOperation cache_remote = null;
	private ContainerOperation container_remote = null;
	private DataObjectOperation object_remote = null;
	private DataSliceOperation slice_remote = null;
	private EntityDataOperation data_remote = null;
	private LocalDataObjectOperation local = null;
	private LocalContainerOperation container_local = null;
	
	public DataObjectService(Principal principal){
		this.principal = principal;
	}

	// MODE = 0: 表示单机版。
	// MODE = 1: 表示系统中无独立的缓存服务器，元数据和缓存服务器都放置与执行服务器中。
	// MODE = 2 ：表示系统中存在多台缓存服务器，元数据分散与执行服务本地磁盘中，但缓存服务器独立。
	// MODE = 3: 表示元数据存储于独立共享存储，但缓存分散在执行服务器集群。
	// MODE = 4: 表示元数据存储于独立存储中，缓存存在多台缓存服务器中，均与执行服务器分离。

	public void createDataObject(InputDataObject object)
			throws InternalException {
		// 检查参数
		String container = object.getContainerName();
		if (object.getObjectUri() == null
				|| container == null) {
			throw new InternalException(ErrorCodes.InvalidArgument);
		}
		if (object.getContent() == null) {
			return;
		}

		// 获得对象的UUID
		String uri = container + "/" + object.getObjectUri();
		// 根据文件后缀获得文件类型
//		String filetype = null;
//		int typeIndex = uri.lastIndexOf(".");
//		if (typeIndex > 0) {
//			filetype = uri.substring(typeIndex - 1);
//		}
		String uuid = JuuidUtils.getuuid(uri);

		Properties pros = new Properties();
		pros.setProperty("java.naming.factory.initial", "org.jnp.interfaces.NamingContextFactory");
		if (MODE == 0) {// 表示单机版
			try {
				ctx = new InitialContext();
				local = (LocalDataObjectOperation)ctx.lookup("DataObjectBean/local");
//				local = new DataObjectBean();
				local.createDataObject(object);
			} catch (NamingException e1) {
				e1.printStackTrace();
			}
		} else if (MODE == 1) { // 缓存与元数据分散到执行服务器集群上
			Node container_node = KetamaNodeLocator.getLocator().getPrimary(
					object.getContainerName());
			pros.setProperty("java.naming.provider.url", container_node.getHost() + ":" + "1099");
			
			try {
				ctx = new InitialContext(pros);
				container_remote = (ContainerOperation)ctx.lookup("ContainerBean/remote");
				// 检查容器的合法性
				MetaContainer metafile = container_remote.getMetaContainer(container);
				
				if (metafile == null) {
					throw new NotFoundException(ErrorCodes.NoSuchContainer);
				}

				// 检查请求的ACL合法性
				if (metafile.getStatus() != ContainerStatus.Working.getValue()) {
					// 目前暂时支持只有容器状态为正常时，才允许上传数据
					throw new ForbiddenException(ErrorCodes.OperationAborted);
				}
				if (!metafile.getAccessKey().equals(object.getAccessKey())) {
					// 如果不等于容器的Owner，需要检查ACL
					Map<String, String> acl = metafile.getAccessControlList();
					String permission = acl.get(object.getAccessKey());
					if (!permission.contains("W")) {// “W”表示具有写入权限
						// 拒绝请求
						throw new ForbiddenException(ErrorCodes.AccessDenied);
					}
				}
			} catch (NamingException e) {
				e.printStackTrace();
				throw new InternalException(ErrorCodes.InternalError);
			}
			
			Node object_note = KetamaNodeLocator.getLocator().getPrimary(uuid);
			pros.setProperty("java.naming.provider.url", object_note.getHost() + ":" + "1099");
			
			MetaDataObject objectFile = null;
			try {
				ctx = new InitialContext(pros);
				object_remote = (DataObjectOperation)ctx.lookup("DataObjectBean/remote");
				objectFile = object_remote.getMetaDataObject(uuid);
			} catch (NamingException e) {
				e.printStackTrace();
				throw new InternalException(ErrorCodes.InternalError);
			}
			
			// this.createContainer(container, node.getHost());
			// 判断版本
			// 通过UUID获得对象的元数据
			if (objectFile != null) {
				// 对象已经存在，检查是否是第一片，如果是，将数据当作是新版本。
				if (object.getOffset() == 0
						&& objectFile.getStatus() != DataObjectStatus.Unfinished
								.getValue()) {
					// objectFile.setNewVersion(null, md5, uuid);
					objectFile.setNewVersion(uuid);// 新增新版本
				}

				// // 在当前版本进行需要追写
				// if (objectFile.getStatus() == DataObjectStatus.Unfinished
				// .getValue()) {
				// // objectFile.setNewSlice(md5, uuid);
				// }
				// if (object.isLastSlice()) {
				// // 最后一片写入成功，则修改对象的状态为可用状态
				// objectFile.setStatus(DataObjectStatus.Working.getValue());
				// }
			} else {
				objectFile = new MetaDataObject();
				objectFile.setAccessKey(object.getAccessKey());
				objectFile.setUuid(uuid);
				objectFile.setNewVersion(uuid);// 新增新版本
				// objectFile
				// .setStatus(object.isLastSlice() ? DataObjectStatus.Working
				// .getValue() : DataObjectStatus.Unfinished
				// .getValue());
				// objectFile.setCreationDate(new Date());// TODO 时间采用GMT
			}

			// 写数据到本地存储中
			String md5 = null;
			byte[] buf_request = new byte[16 * 1024];
			// FIXME 如果没有4M，是否会有问题，待测试
			int bytesRead = 0;
			// 保存分片记录
			List<MetaSliceData> dsmfs = new ArrayList<MetaSliceData>();
			try {
				// 将数据分片进行存储
				// byte[] md5Hash = null;
				ByteArrayOutputStream swapStream = new ByteArrayOutputStream();
				long total_size = 0;
				long slice_size = 0;
				while ((bytesRead = object.getContent().read(buf_request, 0,
						buf_request.length)) != -1) {
					slice_size = slice_size + bytesRead;
					total_size = total_size + slice_size;
					System.out.println(bytesRead);
					// md5 = MD5Util.getMD5(buf_request);
					if (slice_size <= 4 * 1024 * 1024) {
						swapStream.write(buf_request, 0, bytesRead);
					} else {
						byte[] slice_data = swapStream.toByteArray();
						InputStream input_slice = new ByteArrayInputStream(
								slice_data);
						byte[] slice_md5Hash = ServiceUtils
								.computeMD5Hash(input_slice);
						md5 = ServiceUtils.toBase64(slice_md5Hash);
						// 判断数据块是否已经存在
						MetaSliceData sliceMeta = slice_remote
								.getMetaSliceData(md5);
						if (sliceMeta == null) {
							// 数据块不存在，则写新数据块到存储，并增加对数据块的元数据索引文件
							if (SHARE_MODE != 1) {//Share-Nothing
								Node slice_node = KetamaNodeLocator.getLocator().getPrimary(md5);
								pros.setProperty("java.naming.provider.url", slice_node.getHost() + ":" + "1099");
								try {
									ctx = new InitialContext(pros);
									slice_remote = (DataSliceOperation)ctx.lookup("DataSliceBean/remote");
									slice_remote.createDataSlice(md5, buf_request);
								} catch (NamingException e) {
									e.printStackTrace();
								}
							} else {// 共享模式
//								local.createDataSlice(md5, buf_request);
							}
							sliceMeta = new MetaSliceData();
							sliceMeta.setMd5(md5);
							sliceMeta.addReference(1);
							sliceMeta.addVersion(objectFile
									.getCurrentVersionId());
						} else {
							// 数据块以及数据库索引文件存在，则只添加对数据块的引用关系
							List<String> ls_vo = sliceMeta.getVersionObject();
							if (ls_vo == null || ls_vo.size() == 0) {
								// FIXME 正常系统不会走到该流程，执行该代码表示系统有问题
								sliceMeta.addReference(1);
								sliceMeta.addVersion(objectFile
										.getCurrentVersionId());
							} else {
								// 如果上传的版本对象不在列表中，
								if (!ls_vo.contains(objectFile
										.getCurrentVersionId())) {
									sliceMeta.addReference(1);
									sliceMeta.addVersion(objectFile
											.getCurrentVersionId());
								}
							}
						}
						
						// 将数据块索引文件放入列表，以便后续将片刷新到对象的元数据文件中
						dsmfs.add(sliceMeta);

						// 重置新的Slice文件信息
						swapStream.close();
						slice_size = bytesRead;
						swapStream = new ByteArrayOutputStream();
						swapStream.write(buf_request, 0, bytesRead);
					}
				}

				if (slice_size > 0) {
					byte[] slice_data = swapStream.toByteArray();
					InputStream input_slice = new ByteArrayInputStream(
							slice_data);
					byte[] slice_md5Hash = ServiceUtils
							.computeMD5Hash(input_slice);
					md5 = ServiceUtils.toBase64(slice_md5Hash);
					// 判断数据块是否已经存在
					MetaSliceData sliceMeta = slice_remote
							.getMetaSliceData(md5);
					if (sliceMeta == null) {
						// 数据块不存在，则写新数据块到存储，并增加对数据块的元数据索引文件
						if (SHARE_MODE != 1) {
							Node slice_node = KetamaNodeLocator.getLocator().getPrimary(md5);
							pros.setProperty("java.naming.provider.url", slice_node.getHost() + ":" + "1099");
							try {
								ctx = new InitialContext(pros);
								slice_remote = (DataSliceOperation)ctx.lookup("DataSliceBean/remote");
								slice_remote.createDataSlice(md5, buf_request);
							} catch (NamingException e) {
								e.printStackTrace();
								throw new InternalException(ErrorCodes.InternalError);
							}
						} else {// API，下面使用分布式存储系统，如TFS
//							local.createDataSlice(md5, buf_request);
						}
						sliceMeta = new MetaSliceData();
						sliceMeta.setMd5(md5);
						sliceMeta.addReference(1);
						sliceMeta.addVersion(objectFile.getCurrentVersionId());
					} else {
						// 数据块以及数据库索引文件存在，则只添加对数据块的引用关系
						List<String> ls_vo = sliceMeta.getVersionObject();
						if (ls_vo == null || ls_vo.size() == 0) {
							// FIXME 正常系统不会走到该流程，执行该代码表示系统有问题
							sliceMeta.addReference(1);
							sliceMeta.addVersion(objectFile
									.getCurrentVersionId());
						} else {
							// 如果上传的版本对象不在列表中，
							if (!ls_vo.contains(objectFile
									.getCurrentVersionId())) {
								sliceMeta.addReference(1);
								sliceMeta.addVersion(objectFile
										.getCurrentVersionId());
							}
						}
					}
					// 将数据块索引文件放入列表，以便后续将片刷新到对象的元数据文件中
					dsmfs.add(sliceMeta);
					// TODO 增加垃圾数据列表，当索引文件成功创建后，清除该列表。
				}
				for (MetaSliceData dsmf : dsmfs) {
					// 写数据块索引文件
					slice_remote.createMetaSliceData(dsmf);
					objectFile.setNewSlice(dsmf.getMd5());
				}

				// 写数据的元数据文件
				local.createMetaDataObject(objectFile);

				// TODO 将对象的元数据放入缓存

			} catch (NoSuchAlgorithmException e1) {
				e1.printStackTrace();
				throw new InternalException(ErrorCodes.InternalError);
			} catch (IOException e1) {
				e1.printStackTrace();
				throw new InternalException(ErrorCodes.InternalError);
			}
		} else if (MODE == 2) { // 独立缓存集群，元数据分散在执行服务器的本地磁盘。

		} else if (MODE == 3) { // 元数据使用独立共享存储，缓存分散到各个执行服务器集群

		} else if (MODE == 4) { // 元数据使用独立存储，缓存存在多台缓存服务器中，均与执行服务器分离。

		} else {
			// TODO 系统配置有错误，告警，并生成日志
			throw new InternalException(ErrorCodes.InternalError);
		}
	}

	/**
	 * 在本地节点中检查容器元数据是否存在
	 * 
	 * @param containerName
	 *            容器名
	 * @return
	 * @throws NoSuchAlgorithmException
	 */
	public boolean exist(String container) throws NoSuchAlgorithmException {
		// 获得容器元数据文件的UUID
		String fileuuid = JuuidUtils.getuuid(container);

		if (existMetaFile(fileuuid)) {
			// 如果文件存在
			return true;
		}
		// 如果文件不存在
		return false;
	}

	/**
	 * 向本地节点写入元数据文件
	 * 
	 * @param container
	 */
	public void writeMetadataFile(Container container) {
		// 装配容器的元数据文件对象
		MetaContainer metafile = new MetaContainer();
		// 设置元数据文件的文件UUID
		metafile.setUuid(JuuidUtils.getuuid(container.getName()));
		metafile.setMetadata(container.getUserMetadata());
		metafile.setCreationDate(container.getCreationDate());
		metafile.setAccessKey(container.getAccessKey());
		metafile.setSites(container.getSites());

		// 将容器的元数据文件对象序列化为文件
		toXMLFile(metafile);
	}

	/**
	 * 序列化元数据文件对象到磁盘
	 * 
	 * @param container
	 */
	private String toXMLFile(MetaContainer metafile) {
		try {
			String uuidpath = JuuidUtils.getuuidPath(metafile.getUuid());
			String filepath = Constants.MATA_CONTAINER_ROOT + File.separator + uuidpath
					+ ".xml";
			File folder = new File(filepath);
			if (!folder.exists()) {
				folder.mkdirs();
			}
			// 序列化为文件
			SerializeUtil.serializeToXml(metafile, filepath);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}

	/**
	 * 检查本地磁盘上是否具有指定容器的元数据文件
	 * 
	 * @param container
	 */
	private boolean existMetaFile(String fileuuid) {
		String uuidpath = JuuidUtils.getuuidPath(fileuuid);
		String filepath = Constants.MATA_CONTAINER_ROOT + File.separator + uuidpath
				+ ".xml";
		File file = new File(filepath);
		if (file.exists()) {
			return true;
		}
		return false;
	}

	public void deleteDataObject(String container, String objectUri) {
		Properties pros = new Properties();
		pros.setProperty("java.naming.factory.initial", "org.jnp.interfaces.NamingContextFactory");
		if (MODE == 0) {// 表示单机版
			try {
				ctx = new InitialContext();
				local = (LocalDataObjectOperation)ctx.lookup("DataObjectBean/local");
//				local = new DataObjectBean();
				local.deleteDataObject(container, objectUri, this.principal);
			} catch (NamingException e1) {
				throw new InternalException(ErrorCodes.InternalError);
			}
		} else if (MODE == 1) { // 缓存与元数据分散到执行服务器集群上
			
		} else {
			
		}
	}

	public DataObject getDataObject(String container, String objectUri) {
		// 检查参数
		if (container == null
				|| objectUri == null) {
			throw new InternalException(ErrorCodes.InvalidArgument);
		}

		Properties pros = new Properties();
		pros.setProperty("java.naming.factory.initial", "org.jnp.interfaces.NamingContextFactory");
		if (MODE == 0) {// 表示单机版
			try {
				ctx = new InitialContext();
				local = (LocalDataObjectOperation)ctx.lookup("DataObjectBean/local");
//				local = new DataObjectBean();
				container_local = (LocalContainerOperation)ctx.lookup("ContainerBean/local");
//				container_local = new ContainerBean();
				// 检查容器的合法性
				MetaContainer metafile = null;
				metafile = container_local.getMetaContainer(container);
				
				if (metafile == null) {
					throw new NotFoundException(ErrorCodes.NoSuchContainer);
				}

				// 检查请求的ACL合法性
				if (metafile.getStatus() != ContainerStatus.Working.getValue()) {
					// 目前暂时支持只有容器状态为正常时，才允许上传数据
					throw new ForbiddenException(ErrorCodes.OperationAborted);
				}
				
				// 获得容器的ACL，如果具有访问容器的权限，则可读取数据，如果不具有
				// 访问容器的权限，则需要检查要对象是否具有可读的ACL权限
				Map<String, String> acl = metafile.getAccessControlList();
				String permission = acl.get("abc@126.com");
				MetaDataObject obj_meta = null;
				if (permission == null || !permission.contains("R")) {// “R”表示具有读权限
					// 拒绝请求,检查对象的ACL
					// 获得对象的UUID
					String uri = container + "/" + objectUri;
					String uuid = JuuidUtils.getuuid(uri);
					obj_meta = local.getMetaDataObject(uuid);
					if(obj_meta == null){
						throw new NotFoundException(ErrorCodes.NoSuchObject);
					}
					acl = obj_meta.getAccessControlList();
					permission = acl.get("abc@126.com");
					if (permission ==null || !permission.contains("R")) {// “R”表示具有读权限
						throw new ForbiddenException(ErrorCodes.AccessDenied);
					}
				}
				
				// 获得对象的元数据，以便获得对象分片
				List<String> slices = null;
				if(obj_meta == null){
					String uri = container + "/" + objectUri;
					String uuid = JuuidUtils.getuuid(uri);
					obj_meta = local.getMetaDataObject(uuid);
					if(obj_meta == null){
						throw new NotFoundException(ErrorCodes.NoSuchObject);
					}
					String highest_version = obj_meta.getCurrentVersionId();
					List<DataObjectVersion> all_version = obj_meta.getVersions();
					for(DataObjectVersion version : all_version){
						if(highest_version.equals(version.getVersionId())){
							slices = version.getSlices();
							break;
						}
					}
				}
				
				// 通过片的MD5值获得片数据实体
				InputStream stream = null;
				DataObject dto = new DataObject();
				dto.setMimetype("binary/octet-stream");
				if (slices != null) {
					Vector<InputStream> vector = new Vector<InputStream>();
					for (String md5 : slices) {
						Node slice_node = KetamaNodeLocator.getLocator().getPrimary(md5);
						pros.setProperty("java.naming.provider.url", slice_node.getHost() + ":" + "1099");
						
//						ctx = new InitialContext(pros);
//						data_remote = (RealDataOperation)ctx.lookup("RealDataBean/remote");
						data_remote = new EntityDataBean();
						
						stream = data_remote.getRealData(md5);
						vector.add(stream);
					}
					
					//将多个流进行合并后输出
					SequenceInputStream minput = new SequenceInputStream(vector.elements());
					dto.setContent(minput);
					return dto;
				}
				return null;
			} catch (NamingException e1) {
				e1.printStackTrace();
				throw new InternalException(ErrorCodes.InternalError);
			}
		} else if (MODE == 1) { // 缓存与元数据分散到执行服务器集群上
			return null;
		} else {
			return null;
		}
	}

	public void setAccessControlPolicy(String container, String objectUri, AccessControlList acl) {
		// TODO Auto-generated method stub
		
	}
}
